﻿/*
* Copyright 2009-2010 AKABANA.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
using System;
using System.Globalization;

namespace Akabana.Amf.Util
{

    public static class TypeUtil
    {

        public static Object CreateInstance(Type type, params object[] args)
        {
            return Activator.CreateInstance(type, args);
        }

        public static object ChangeType(object value, Type changeType, IFormatProvider formatProvider)
        {
            Type convertType;

            if (changeType.IsEnum)
            {
                convertType = Enum.GetUnderlyingType(changeType);
            }
            else
            {
                convertType = changeType;
            }
            if (value == null)
            {
                return null;
            }
            if (value.GetType() == convertType)
            {
                return value;
            }
            if (value.GetType().IsArray)
            {
                var valueType = value.GetType().GetElementType();
                var elementType = convertType.GetElementType();
                if (CanConvertFrom(valueType, elementType, formatProvider))
                {
                    Array array = (Array)value;
                    Array values = Array.CreateInstance(elementType, array.Length);
                    for (int i = 0; i < array.Length; i++)
                    {
                        Object arrayValue = array.GetValue(i);
                        Type arrayValueType = arrayValue.GetType();
                        if (elementType.IsAssignableFrom(arrayValueType))
                        {
                            values.SetValue(arrayValue, i);
                        }
                        else
                        {
                            values.SetValue(Convert.ChangeType(arrayValue, elementType, formatProvider), i);
                        }
                    }
                    return values;
                }
                else
                {
                    return null;
                }
            }

            if (CanConvertFrom(value.GetType(), convertType,formatProvider))
            {
                return Convert.ChangeType(value, convertType, formatProvider);
            }
            else
            {
                return null;
            }
        }

        public static bool CanConvertFrom(Type srcType, Type destType)
        {
            return CanConvertFrom(srcType, destType, CultureInfo.CurrentCulture);
        }

        public static bool CanConvertFrom(Type srcType, Type destType, IFormatProvider formatProvider)
        {
            TypeCode srcTypeCode = Type.GetTypeCode(srcType);
            Type convertType;
            TypeCode convertTypeCode;

            if (destType.IsEnum)
            {
                convertType = Enum.GetUnderlyingType(destType);
            }
            else
            {
                convertType = destType;
            }

            convertTypeCode = Type.GetTypeCode(convertType);
            if (srcTypeCode == convertTypeCode)
            {
                return true;
            }

            if (srcType.IsPrimitive && convertType.IsPrimitive)
            {
                if (srcTypeCode == convertTypeCode)
                {
                    return true;
                }

                // Possible conversions from Char follow.
                if (srcTypeCode == TypeCode.Char)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Char: return true;
                        case TypeCode.Byte: return true;
                        case TypeCode.UInt16: return true;
                        case TypeCode.UInt32: return true;
                        case TypeCode.Int32: return true;
                        case TypeCode.UInt64: return true;
                        case TypeCode.Int64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from Byte follow.
                if (srcTypeCode == TypeCode.Byte)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Byte: return true;
                        case TypeCode.Char: return true;
                        case TypeCode.UInt16: return true;
                        case TypeCode.Int16: return true;
                        case TypeCode.UInt32: return true;
                        case TypeCode.Int32: return true;
                        case TypeCode.UInt64: return true;
                        case TypeCode.Int64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from SByte follow.
                if (srcTypeCode == TypeCode.SByte)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.SByte: return true;
                        case TypeCode.Int16: return true;
                        case TypeCode.Int32: return true;
                        case TypeCode.Int64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from UInt16 follow.
                if (srcTypeCode == TypeCode.UInt16)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.UInt16: return true;
                        case TypeCode.UInt32: return true;
                        case TypeCode.UInt64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from Int16 follow.
                if (srcTypeCode == TypeCode.Int16)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Int16: return true;
                        case TypeCode.Int32: return true;
                        case TypeCode.Int64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from UInt32 follow.
                if (srcTypeCode == TypeCode.UInt32)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.UInt16: return true;
                        case TypeCode.UInt32: return true;
                        case TypeCode.UInt64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from Int32 follow.
                if (srcTypeCode == TypeCode.Int32)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Byte: return true;
                        case TypeCode.Int16: return true;
                        case TypeCode.Int32: return true;
                        case TypeCode.Int64: return true;
                        case TypeCode.Single: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from UInt64 follow.
                if (srcTypeCode == TypeCode.UInt64)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.UInt64: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from Int64 follow.
                if (srcTypeCode == TypeCode.Int64)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Int64: return true;
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }
                // Possible conversions from Single follow.
                if (srcTypeCode == TypeCode.Single)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }

                if (srcTypeCode == TypeCode.Double)
                {
                    switch (convertTypeCode)
                    {
                        case TypeCode.Double: return true;
                        default: return false;
                    }
                }

                return false;
            }
            else
            {
                return convertType.IsSubclassOf(srcType) || convertType.IsAssignableFrom(srcType);
            }
        }

    }
}
